/*
*
==============================================================================
*  Name        : 
*  Part of     : Omen Game Engine - DirectX 10 Plugin
*  Description : 
*  Version     : 1.0
*
*  Copyright (c) 2013 Lauri Kortevaara.
* 
==============================================================================
*/

#define DIRECTX_10_PLUGIN_SOURCE 1

#include "DirectX_10_BasicShader.h"
#include <IOmenFactory.h>

// DirectX 10 Asynchronous API
#include "D3DX10Async.h"
#include "D3DX10Core.h"
#include "D3DX10Math.h"

#include <string>
#include <fstream>
#include <vector>
#include <sstream>

using namespace Omen;

/// <summary></summary>
BasicShader::BasicShader( ID3D10Device* pDevice, 
                        const std::wstring& vertexShaderFileName,
                        const std::wstring& pixelShaderFileName,
                        const std::wstring& geometryShaderFileName ) :
    m_pDevice(pDevice),
    m_shaderFileName(vertexShaderFileName)
{
    m_pShaderEffect = NULL;

    initializeShader();
}

bool BasicShader::isValid()
{
    return m_pShaderEffect != NULL;
}

ID3D10Effect*   BasicShader::effect() const 
{ 
    return m_pShaderEffect; 
}

ID3D10EffectTechnique*   BasicShader::technique() const 
{ 
    return m_pShaderTechnique; 
}

ID3D10ShaderResourceView*&           BasicShader::textureResourceView() 
{
    return m_pTextureRV; 
}

ID3D10EffectMatrixVariable*         BasicShader::worldMatrix() 
{ 
    return m_pWorldVariable; 
}

ID3D10EffectMatrixVariable*         BasicShader::viewMatrix() 
{ 
    return m_pViewVariable; 
}

ID3D10EffectMatrixVariable*         BasicShader::projectionMatrix() 
{ 
    return m_pProjectionVariable; 
}

ID3D10EffectVectorVariable*         BasicShader::eyeVariable() 
{ 
    return m_pEyeVariable; 
}

ID3D10EffectVectorVariable*         BasicShader::meshColor() 
{ 
    return m_pMeshColorVariable; 
}

ID3D10EffectShaderResourceVariable* BasicShader::textureVariable() 
{ 
    return m_pTextureVariable; 
}

void BasicShader::render(const Omen::IRenderer * pRenderer)
{
}

ID3D10InputLayout* BasicShader::getInputLayout() const
{
    return m_pVertexShaderInputLayout;
}

void BasicShader::initializeShader()
{
    LPCWSTR pFileName                       = m_shaderFileName.c_str(); 
    CONST D3D10_SHADER_MACRO *pDefines      = NULL;
    ID3D10Include *pInclude                 = NULL;
    LPCSTR pProfile                         = "fx_4_0";
    UINT HLSLFlags                          = D3D10_SHADER_ENABLE_STRICTNESS;
    UINT FXFlags                            = 0;
    ID3D10Device *pDevice                   = m_pDevice;
    ID3D10EffectPool *pEffectPool           = NULL;
    ID3DX10ThreadPump* pPump                = NULL; 
    ID3D10Effect *ppEffect                 = m_pShaderEffect;
    ID3D10Blob *ppErrors                   = m_pBlobShaderErrors;
    HRESULT* pHResult                       = NULL;

    if( !GetOmenFactory().fileUtils().fileExists( m_shaderFileName ) )
        return;
    
    HRESULT hr = D3DX10CreateEffectFromFile( pFileName, pDefines, pInclude, pProfile, HLSLFlags, FXFlags, pDevice, pEffectPool, pPump, &m_pShaderEffect, &m_pBlobShaderErrors, pHResult );

    if( hr == S_OK )
    {
        // Obtain the technique
        m_pShaderTechnique = m_pShaderEffect->GetTechniqueByName( "Render" );

        // Define the input layout
        const D3D10_INPUT_ELEMENT_DESC layout[] =
        {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
            { "NORMAL",   0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 16, D3D10_INPUT_PER_VERTEX_DATA, 0 },
            { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 32, D3D10_INPUT_PER_VERTEX_DATA, 0 },
        };

        UINT numElements = sizeof( layout ) / sizeof( layout[0] );

        // Create the input layout
        D3D10_PASS_DESC PassDesc;
        m_pShaderTechnique->GetPassByIndex( 0 )->GetDesc( &PassDesc );
        hr = m_pDevice->CreateInputLayout( layout, numElements, PassDesc.pIAInputSignature,
                                           PassDesc.IAInputSignatureSize, &m_pVertexShaderInputLayout );
        if( hr == S_OK )
        {
            // Set the input layout
            m_pDevice->IASetInputLayout( m_pVertexShaderInputLayout );
        }
    }
    else
    {
        MessageBoxA( NULL, (LPCSTR)m_pBlobShaderErrors->GetBufferPointer(), "Error compiling the shader!", 0 );
    }
    
    m_pWorldVariable        = m_pShaderEffect->GetVariableByName( "World" )->AsMatrix();
    m_pViewVariable         = m_pShaderEffect->GetVariableByName( "View" )->AsMatrix();
    m_pProjectionVariable   = m_pShaderEffect->GetVariableByName( "Projection" )->AsMatrix();
    m_pMeshColorVariable    = m_pShaderEffect->GetVariableByName( "vMeshColor" )->AsVector();
    m_pTextureVariable      = m_pShaderEffect->GetVariableByName( "shaderTexture" )->AsShaderResource();
    m_pEyeVariable            = m_pShaderEffect->GetVariableByName( "Eye" )->AsVector();

    // Initialize the world matrix
    D3DXMatrixIdentity( &m_World );
    m_pWorldVariable->SetMatrix( ( float* )&m_World );

    // Initialize the view matrix
    D3DXVECTOR3 Eye( 0.0f, 0.0f, 0.0f );
    D3DXVECTOR3 At( 0.0f, 0.0f, 0.0f );
    D3DXVECTOR3 Up( 0.0f, 1.0f, 0.0f );
    D3DXMatrixLookAtLH( &m_View, &Eye, &At, &Up );

    // Update Variables that never change
    m_pViewVariable->SetMatrix( ( float* )&m_View );

    // Setup the lighting
    //CREATE LIGHTS AND MATERIAL
    //--------------------------------------------------------------------------------
    
    
    struct DirectionalLight
    {
        D3DXVECTOR4 color;
        D3DXVECTOR3 position;
    } ;

    struct Material
    {
        float ambient, diffuse, specular, shininess;
    };

    DirectionalLight directionalLight;

    //set directional light - MAKE sure light direction is a unit vector
    directionalLight.color = D3DXVECTOR4(1.0f,1.0f,1.0f,1.0f);
    directionalLight.position = D3DXVECTOR3(0,0,0);
        
    ID3D10EffectVariable* pVar = m_pShaderEffect->GetVariableByName( "Light" );
    pVar->SetRawValue(&directionalLight, 0, sizeof(DirectionalLight));

    //set variables
    /*material.ambient =  0.2f;
    material.diffuse =  0.8f;
    material.specular = 0.5f;
    material.shininess = 50.0f;

    pVar = m_pShaderEffect->GetVariableByName( "Material" );
    pVar->SetRawValue(&material, 0, sizeof(Material));*/

    D3DXVECTOR4 ambientLight = D3DXVECTOR4(1.0f,1.0f,1.0f,1.0f);
    pVar = m_pShaderEffect->GetVariableByName( "AmbientLight" );
    pVar->SetRawValue( &ambientLight, 0, sizeof(D3DXVECTOR4) );    
}


///
/// <summary>CreateInputLAyoutDescFromVertexShaderSignature will create the input layout description</summary>
///
HRESULT BasicShader::CreateInputLayoutDescFromVertexShaderSignature( ID3DBlob* pShaderBlob, ID3D10Device* pD3DDevice, ID3D10InputLayout** pInputLayout )
{
    // Reflect shader info
    ID3D10ShaderReflection* pVertexShaderReflection = NULL;    
    HRESULT hr = D3D10ReflectShader( pShaderBlob->GetBufferPointer(), pShaderBlob->GetBufferSize(), &pVertexShaderReflection );

    // Get shader info
    D3D10_SHADER_DESC shaderDesc;
    pVertexShaderReflection->GetDesc( &shaderDesc );
    
    // Read input layout description from shader info
    size_t byteOffset = 0;
    
    std::vector<D3D10_INPUT_ELEMENT_DESC> inputLayoutDesc;
    for ( size_t i=0; i< shaderDesc.InputParameters; i++ )
    {
        D3D10_SIGNATURE_PARAMETER_DESC paramDesc;        
        pVertexShaderReflection->GetInputParameterDesc(i, &paramDesc );

        // fill out input element desc
        D3D10_INPUT_ELEMENT_DESC elementDesc;    
        elementDesc.SemanticName = paramDesc.SemanticName;        
        elementDesc.SemanticIndex = paramDesc.SemanticIndex;
        elementDesc.InputSlot = 0;
        elementDesc.AlignedByteOffset = byteOffset;
        elementDesc.InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
        elementDesc.InstanceDataStepRate = 0;    

        // determine DXGI format
        if ( paramDesc.Mask == 1 )
        {
            if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_UINT32 ) elementDesc.Format = DXGI_FORMAT_R32_UINT;
            else if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_SINT32 ) elementDesc.Format = DXGI_FORMAT_R32_SINT;
            else if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_FLOAT32 ) elementDesc.Format = DXGI_FORMAT_R32_FLOAT;
            byteOffset += 4;
        }
        else if ( paramDesc.Mask <= 3 )
        {
            if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_UINT32 ) elementDesc.Format = DXGI_FORMAT_R32G32_UINT;
            else if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_SINT32 ) elementDesc.Format = DXGI_FORMAT_R32G32_SINT;
            else if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_FLOAT32 ) elementDesc.Format = DXGI_FORMAT_R32G32_FLOAT;
            byteOffset += 8;
        }
        else if ( paramDesc.Mask <= 7 )
        {
            if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_UINT32 ) elementDesc.Format = DXGI_FORMAT_R32G32B32_UINT;
            else if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_SINT32 ) elementDesc.Format = DXGI_FORMAT_R32G32B32_SINT;
            else if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_FLOAT32 ) elementDesc.Format = DXGI_FORMAT_R32G32B32_FLOAT;
            byteOffset += 12;
        }
        else if ( paramDesc.Mask <= 15 )
        {
            if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_UINT32 ) elementDesc.Format = DXGI_FORMAT_R32G32B32A32_UINT;
            else if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_SINT32 ) elementDesc.Format = DXGI_FORMAT_R32G32B32A32_SINT;
            else if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_FLOAT32 ) elementDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
            byteOffset += 16;
        }
            
        //save element desc
        inputLayoutDesc.push_back(elementDesc);
    }        

    // Try to create Input Layout
    hr = pD3DDevice->CreateInputLayout( &inputLayoutDesc[0], inputLayoutDesc.size(), pShaderBlob->GetBufferPointer(), pShaderBlob->GetBufferSize(), pInputLayout );

    //Free allocation shader reflection memory
    pVertexShaderReflection->Release();
    return hr;
}


/// Vertex Shader

/// <summary></summary>
void BasicShader::setVertexShaderPtr( void* pShader )
{
    //m_pVertexShader = reinterpret_cast<ID3D10VertexShader*>(pShader);
}

/// <summary></summary>
void* BasicShader::getVertexShaderPtr()
{
    //return m_pVertexShader;
    return NULL;
}

/// <summary></summary>
void BasicShader::setVertexShaderSource( wchar_t* pSource, unsigned long size )
{
    //m_vertexShaderSource = pSource;
}

/// <summary></summary>
void BasicShader::getVertexShaderSource( wchar_t* pSource, unsigned long maxSize )
{
    /*unsigned long sourceLength = min(maxSize,m_vertexShaderSource.length() );
    memcpy( pSource, m_vertexShaderSource.c_str(), sourceLength*sizeof(wchar_t) );*/
}

/// Pixel Shader
void BasicShader::setPixelShaderPtr( void* pShader )
{
    //m_pPixelShader = reinterpret_cast<ID3D10PixelShader*>(pShader);
}

/// <summary></summary>
void* BasicShader::getPixelShaderPtr()
{
    //return m_pPixelShader;
    return NULL;
}

/// <summary></summary>
void BasicShader::setPixelShaderSource( wchar_t* pSource, unsigned long size )
{
    //m_pixelShaderSource = pSource;
}

/// <summary></summary>
void BasicShader::getPixelShaderSource( wchar_t* pSource, unsigned long maxSize )
{
    /*unsigned long sourceLength = min(maxSize,m_pixelShaderSource.length() );
    //memcpy( pSource, m_pixelShaderSource.c_str(), sourceLength*sizeof(wchar_t) );
    */
}


/// Geometry Shader

/// <summary></summary>
void BasicShader::setGeometryShaderPtr( void* pShader )
{
    //m_pGeometryShader = reinterpret_cast<ID3D10GeometryShader*>(pShader);
}

/// <summary></summary>
void* BasicShader::getGeometryShaderPtr()
{
    //return m_pGeometryShader;
    return NULL;
}

/// <summary></summary>
void BasicShader::setGeometryShaderSource( wchar_t* pSource, unsigned long size )
{
    //m_geometryShaderSource = pSource;
}

/// <summary></summary>
void BasicShader::getGeometryShaderSource( wchar_t* pSource, unsigned long maxSize )
{
    /*unsigned long sourceLength = min(maxSize,m_geometryShaderSource.length() );
    //memcpy( pSource, m_geometryShaderSource.c_str(), sourceLength*sizeof(wchar_t) );
    */
}

void BasicShader::setTexture( const std::wstring& texturePath )
{
    // Load the Texture

    HRESULT hr = D3DX10CreateShaderResourceViewFromFile( m_pDevice, 
                                                            texturePath.c_str(),
                                                            NULL, 
                                                            NULL, 
                                                            &textureResourceView(), 
                                                            NULL 
                                                            );
    if( hr == S_OK )
    {
        m_pTextureVariable->SetResource( m_pTextureRV );
    }
}

/// EOF